#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#include <psp2/kernel/sysmem.h>
#include <psp2/display.h>

#include "scr_print.h"
#include "font.h"

#define SCREEN_WIDTH 960
#define SCREEN_HEIGHT 544
#define SCREEN_FB_WIDTH 960
#define SCREEN_FB_SIZE (2 * 1024 * 1024)

#define TAB_SIZE 4u
#define WORD_SPACE 0u
#define LINE_SPACE 4u

static int s_pen_x = 0, s_pen_y = 0;
static uint32_t s_bg_col = 0xFF000000, s_fg_col = 0xFFFFFFFF;
static void *s_vram_base;

int utf8_to_ucs2(const char *utf8, unsigned int *character)
{
	if (((utf8[0] & 0xF0) == 0xE0) && ((utf8[1] & 0xC0) == 0x80) && ((utf8[2] & 0xC0) == 0x80))
	{
		*character = ((utf8[0] & 0x0F) << 12) | ((utf8[1] & 0x3F) << 6) | (utf8[2] & 0x3F);
		return 3;
	}
	else if (((utf8[0] & 0xE0) == 0xC0) && ((utf8[1] & 0xC0) == 0x80))
	{
		*character = ((utf8[0] & 0x1F) << 6) | (utf8[1] & 0x3F);
		return 2;
	}
	else
	{
		*character = utf8[0];
		return 1;
	}
}

int scrPrintInit()
{
	int res;

	int block = sceKernelAllocMemBlock("display", SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCREEN_FB_SIZE, NULL);
	sceKernelGetMemBlockBase(block, &s_vram_base);

	SceDisplayFrameBuf framebuf;
	framebuf.size = sizeof(framebuf);
	framebuf.base = s_vram_base;
	framebuf.pitch = SCREEN_FB_WIDTH;
	framebuf.width = SCREEN_WIDTH;
	framebuf.height = SCREEN_HEIGHT;
	framebuf.pixelformat = SCE_DISPLAY_PIXELFORMAT_A8B8G8R8;
	sceDisplaySetFrameBuf(&framebuf, 1);

	res = fontInit();

	s_pen_x = s_pen_y = 0;

	return res;
}

int scrPrintFinish()
{
	fontFinish();

	return 0;
}

void scrPrintClear()
{
	s_pen_x = s_pen_y = 0;
	int i;
	for (i = 0; i < SCREEN_FB_WIDTH * SCREEN_HEIGHT; i++)
	{
		((uint32_t *)s_vram_base)[i] = s_bg_col;
	}
}

void scrPrintSetBackColor(uint32_t color)
{
	s_bg_col = color;
}

void scrPrintSetTextColor(uint32_t color)
{
	s_fg_col = color;
}

int scrPrintGetX()
{
	return s_pen_x;
}

int scrPrintGetY()
{
	return s_pen_y;
}

void scrPrintSetXY(int x, int y)
{
	s_pen_x = x;
	s_pen_y = y;
}

int scrPrintText(const char *text)
{
	unsigned int character;
	unsigned char *font, *font_ptr;
	int word_width, word_height;
	int word_width_bytes;
	uint32_t *vram, *vram_ptr;

	int font_size = fontGetFontSize();

	int i;
	for (i = 0; text[i];)
	{
		i += utf8_to_ucs2(&text[i], &character);

		if (character == '\n')
		{
			s_pen_y += (font_size + LINE_SPACE);
			s_pen_x = 0;
			continue;
		}
		else if (character == '\t')
		{
			int j;
			for (j = 0; j < TAB_SIZE; j++)
			{
				scrPrintText(" ");
			}
			continue;
		}

		font = fontGetData(character, &word_width, &word_height, &word_width_bytes);
		if (!font)
			continue;

		if (s_pen_x + word_width > SCREEN_WIDTH)
		{
			s_pen_y += (font_size + LINE_SPACE);
			s_pen_x = 0;
		}
		if (s_pen_y + font_size > SCREEN_HEIGHT)
		{
			scrPrintClear();
		}

		int y = s_pen_y + (font_size - word_height);
		vram = (uint32_t *)s_vram_base + s_pen_x + y * SCREEN_FB_WIDTH;
		int p;
		int row, col;
		for (row = 0; row < word_height; row++)
		{
			vram_ptr = vram;
			font_ptr = font;
			p = 0;
			for (col = 0; col < word_width; col++)
			{
				*vram_ptr++ = (*font_ptr & (0x80 >> p)) ? s_fg_col : s_bg_col;
				if (++p > 7)
				{
					font_ptr++;
					p = 0;
				}
			}
			font += word_width_bytes;
			vram += SCREEN_FB_WIDTH;
		}
		s_pen_x += (word_width + WORD_SPACE);
	}

	return i;
}

void scrPrintfText(const char *format, ...)
{
	char buf[512];
	va_list opt;
	va_start(opt, format);
	vsnprintf(buf, sizeof(buf), format, opt);
	scrPrintText(buf);
	va_end(opt);
}
